package unifor.mia.redes.relprotocol.channel;

import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

import unifor.mia.redes.relprotocol.msg.Mensagem;

public class Channel {

	private static final long serialVersionUID = -1502215318788279499L;
	private static final LinkedBlockingQueue<Mensagem> commChannel = new LinkedBlockingQueue<Mensagem>(); 
	private static final LinkedBlockingQueue<String> controlChannel = new LinkedBlockingQueue<String>(); 
	
	private static final Random randomGenerator = new Random();

	private static int qtosErrosACK = 0;
	
	public static void write(Mensagem msg){

		try {
			// Para simular um canal de comunicação não confiável, inserimos erros randomicamente nos pacotes
			Mensagem m = inserirErro(msg);
			commChannel.put(m);
			System.out.println("[CHANNEL] Mensagem enviada: #(" + m.getSeq() + ") => HEADER(" + m.getHeader() + ") => BODY(" + m.getBody() + ") => HASH(" + m.getHash() + ")");
		} catch (InterruptedException e) {
			//nada a fazer se thread for interrompida
			return;
		}
	}

	public static Mensagem read(){
		try {
			Mensagem m = commChannel.take();
			System.out.println("[CHANNEL] Mensagem recebida: #(" + m.getSeq() + ") => HEADER(" + m.getHeader() + ") => BODY(" + m.getBody() + ") => HASH(" + m.getHash() + ")");
			return m;
		} catch (InterruptedException e) {
			Mensagem m = new Mensagem();
			m.setBody("");
			return m;
		}
	}

	private static Mensagem inserirErro(Mensagem msg) {
		// Distribuição de erros. A quantidade de 1's
		// indica a probabilidade de incidencia de erros.
		// Assim, 3 valores 1 no array = 30% (3/10)
		int[] a = { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 };
		int randomInt = randomGenerator.nextInt(10);
		
		// Se ocorreu incidencia de erro
		if (a[randomInt] == 1) {
			// a fim de proteger a referencia do Sender para 
			// a mensagem, criamos novo objeto e copiamos o estado
			Mensagem m = new Mensagem();
			m.setHeader(new String(msg.getHeader()));
			m.setBody(new String(msg.getBody().getBytes()).concat("ERRO"));
			m.setHash(new String(msg.getHash()));
			m.setSeq(msg.getSeq());
			if (msg.starting()) m.setStarting();
			if (msg.end()) m.setEnding();
			return m;
		}
		return msg;
	}

	
	public static void ACK(){
		try {
			if(qtosErrosACK++ <= 1){
				controlChannel.put("AC");
				System.out.println("[Control] AC from Receiver");
			}else {
				controlChannel.put("ACK");
				System.out.println("[Control] ACK from Receiver");
			}
		} catch (InterruptedException e) {
			//nada a fazer se thread for interrompida
			return;
		}
	}
	
	public static void NAK(){
		try {
			controlChannel.put("NAK");
			System.out.println("[Control] NAK from Receiver");
		} catch (InterruptedException e) {
			//nada a fazer se thread for interrompida
			return;
		}
	}
	
	public static String waitForConfirmation(){
		try {
			String s = controlChannel.take();
			System.out.println("[Control] " + s + " received on Sender");
			return s;
		} catch (InterruptedException e) {
			return "";
		}
	}

}
